#region MIT Licence
/*
 * Copyright (C) 2014 PdfCreative
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom 
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

using PdfCreative.Pdf.Writer.Fonts.TrueType;

namespace PdfCreative.Pdf.Writer.Fonts
{
    /// <summary>
    /// The TrueTypeFontInfo class represents a TrueType font that is loaded from a file.
    /// It contains properties on the font, and a list of glyph properties and measurements.
    /// </summary>
    public class TrueTypeFontInfo : FontInfo
    {
        // Private fields
        // ==============
        #region Private fields
        private decimal _revision;                      // The revision of the TrueType file
        private DateTime _dateCreated;                  // The creation date of the font
        private DateTime _dateModified;                 // The date it was modified
        private string _fullName;                       // The font's full name.
        private int _xMin;                              // X min metric for the entire font
        private int _yMin;                              // Y min metric for the entire font
        private int _xMax;                              // X max metric for the entire font
        private int _yMax;                              // Y max metric for the entire font
        private int _unitsPerEm;                        // The number of units that make up one em-point
        private int _ascender;                          // The ascender of the font
        private int _descender;                         // The descender of the font
        private decimal _italicAngle;                   // The font's italic angle
        private int _capHeight;                         // The height of capitals in the font
        private int _stemV;                             // The width of vertical stems in the font
        private bool _isSymbolFont = false;             // Is this font a symbol font?
        private string _postscriptName;                 // The font's postscript name
        private Dictionary<int, GlyphInfo> _mappedGlyphs;
                                                        // The character-to-glyph mapping
        private GlyphInfo _undefinedGlyph;              // The glyph that represents an 'undefined' mapping
        #endregion




        // Public overrides
        // ================
        #region FontInfo properties
        /// <summary>
        /// Retrieves the font's ascent
        /// </summary>
        public override int Ascent
        {
            get { return _ascender; }
        }

        /// <summary>
        /// Retrieves the height of capitals in this font
        /// </summary>
        public override int CapHeight
        {
            get { return _capHeight; }
        }

        /// <summary>
        /// Retrieves the descent of this font.
        /// </summary>
        public override int Descent
        {
            get { return _descender; }
        }

        /// <summary>
        /// Retrieves the font's flags as demanded by the PDF specification.
        /// </summary>
        public override int Flags
        {
            get
            {
                return _isSymbolFont ? 4 : 32;
            }
        }

        /// <summary>
        /// Retrieves the italic angle.
        /// </summary>
        public override decimal ItalicAngle
        {
            get { return _italicAngle; }
        }

        /// <summary>
        /// Retrieves the font's postscript name.
        /// </summary>
        public override string PostscriptName
        {
            get { return _postscriptName; }
        }

        /// <summary>
        /// Retrieves the size of vertical stems in the font.
        /// </summary>
        public override int StemV
        {
            get { return _stemV; }
        }

        /// <summary>
        /// Retrieves the number of units per Em that the measurements of this font are based in.
        /// </summary>
        public override int UnitsPerEm
        {
            get { return _unitsPerEm; }
        }

        /// <summary>
        /// Retrieves the right-most boundary of the font's bounding-box.
        /// </summary>
        public override int XMax
        {
            get { return _xMax; }
        }

        /// <summary>
        /// Retrieves the left-most boundary of the font's bounding-box.
        /// </summary>
        public override int XMin
        {
            get { return _xMin; }
        }

        /// <summary>
        /// Retrieves the upper boundary of the font's bounding-box.
        /// </summary>
        public override int YMax
        {
            get { return _yMax; }
        }

        /// <summary>
        /// Retrieves the lower boundary of the font's bounding-box.
        /// </summary>
        public override int YMin
        {
            get { return _yMin; }
        }
        #endregion

        #region FontInfo methods
        /// <summary>
        /// Retrieves the width of the specified character.
        /// </summary>
        /// <param name="ch">The character</param>
        /// <returns>The width of the character</returns>
        public override int GetCharacterWidth(char ch)
        {
            if (_mappedGlyphs.ContainsKey((int)ch))
                return _mappedGlyphs[(int)ch].Metric.AdvanceWidth;
            else if(null!=_undefinedGlyph)
                return _undefinedGlyph.Metric.AdvanceWidth;
            else
                return 0;
        }

        /// <summary>
        /// Parses the font file.
        /// </summary>
        /// <param name="filename">The name of the file to parse</param>
        /// <exception cref="Type1.Type1FontParseException">An error occurred while parsing the font file. See the
        /// exception's InnerException for more details, where available.</exception>
        public override void Parse(string filename)
        {
            if (null == filename) throw new ArgumentNullException("filename");

            Parse(File.OpenRead(filename));
        }

        /// <summary>
        /// Parses the data contained in the stream. The stream must support seeking and data will be read from
        /// the start of the stream.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <exception cref="Type1.Type1FontParseException">An error occurred while parsing the font file. See the
        /// exception's InnerException for more details, where available.</exception>
        public override void Parse(Stream stream)
        {
            if (null == stream) throw new ArgumentNullException("stream");

            ParseFontStream(stream, false);
        }

        /// <summary>
        /// Parses the headers of the font file. The requirement for this is that the font's name is read. No
        /// other data is required to be read.
        /// </summary>
        /// <param name="filename">The name of the file to parse</param>
        public override void ParseHeaders(string filename)
        {
            if (null == filename) throw new ArgumentNullException("filename");

            ParseHeaders(File.OpenRead(filename));
        }

        /// <summary>
        /// Parses the data contained in the stream. The stream must support seeking and data will be read from
        /// the start of the stream. Only the font's headers are read.
        /// The requirement for this is that the font's name is read. No
        /// other data is required to be read.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        public override void ParseHeaders(Stream stream)
        {
            if (null == stream) throw new ArgumentNullException("stream");

            ParseFontStream(stream, true);
        }
        #endregion



        // Public properties
        // =================
        #region Public properties
        /// <summary>
        /// Retrieves the creation date of the font.
        /// </summary>
        public DateTime DateCreated
        {
            get { return _dateCreated; }
        }

        /// <summary>
        /// Retrieves the modification date of the font.
        /// </summary>
        public DateTime DateModified
        {
            get { return _dateModified; }
        }

        /// <summary>
        /// Retrieves the font's full name.
        /// </summary>
        public string FullName
        {
            get { return _fullName; }
        }

        #endregion



        // Private implementation
        // ======================
        #region Parsing data
        /// <summary>
        /// Finds the table with the specified tag.
        /// </summary>
        /// <param name="tables">The list of tables to search</param>
        /// <param name="tag">The tag to look for</param>
        /// <returns>Reference to the table with the specified tag, if it exists, or a null reference if no
        /// table with that tag was found.</returns>
        private Table FindTable(Table[] tables, string tag)
        {
            Debug.Assert(null != tables);
            Debug.Assert(null != tag);

            Table result = null;
            foreach (Table table in tables) if (table.Tag == tag) { result = table; break; }

            return result;
        }

        /// <summary>
        /// Retrieves the glyph for the specified character.
        /// </summary>
        /// <param name="character">The character</param>
        /// <returns>A reference to a GlyphInfo instance for the corresponding glyph, 
        /// or a null reference if no glyph exists for that character.</returns>
        private GlyphInfo GetGlyph(char character)
        {
            if (_mappedGlyphs.ContainsKey((int)character))
                return _mappedGlyphs[(int)character];
            else
                return _undefinedGlyph;
        }

        /// <summary>
        /// Parses the data contained in the stream. 
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="headersOnly">True to read only the file headers.
        /// False to parse the entire file.</param>
        private void ParseFontStream(Stream stream, bool headersOnly)
        {
            Debug.Assert(null != stream);

            try
            {
                // Rewind the stream.
                stream.Seek(0, SeekOrigin.Begin);

                // Read the offset table. This returns the number of tables.
                int tableCount = ReadOffsetTable(stream);
                if (tableCount > 0)
                {
                    // Read the tables off the directory.
                    Table[] tables = ReadTableDirectory(stream, tableCount);
                    if (null != tables)
                    {
                        // Read the font headers to store global font properties.
                        FontHeaders headers = ReadFontHeaders(stream, tables);
                        int horizontalMetricsCount = ReadHorizontalHeaders(stream, tables);
                        ReadPostscriptData(stream, tables);
                        ReadOs2Data(stream, tables);
                        ReadNameData(stream, tables);

                        if (false == headersOnly)
                        {
                            // Read character mappings. This dictionary will map ASCII character values to glyph indices.
                            CharacterMapping[] mappings = ReadCharacterMappings(stream, tables);

                            // We determine the number of glyphs in this file.
                            int glyphCount = ReadMaximumProfile(stream, tables);

                            // We now read the glyph data
                            // Read the horizontal metrics for the glyphs.
                            HorizontalMetric[] metrics = ReadHorizontalMetrics(stream, tables, horizontalMetricsCount, glyphCount);
                            GlyphInfo[] glyphs = ReadGlyphs(glyphCount, stream, tables, headers, metrics);
                            if (null != glyphs && glyphs.Length > 0)
                            {
                                _undefinedGlyph = glyphs[0];

                                // Add the glyphs to the mapping.
                                if (null != mappings)
                                    foreach (CharacterMapping mapping in mappings)
                                    {
                                        if (mapping.GlyphIndex <= glyphs.Length && mapping.GlyphIndex > 0)
                                            _mappedGlyphs[mapping.Character] = glyphs[mapping.GlyphIndex - 1];
                                    }

                                // Finally, if we have no capHeight or stemV set at the moment we create these values from
                                // representative glyphs.
                                if (0 == _capHeight)
                                {
                                    GlyphInfo capitalH = this.GetGlyph('H');
                                    if (null != capitalH) _capHeight = capitalH.YMax;
                                }

                                if (0 == _stemV)
                                {
                                    GlyphInfo smallCapsI = this.GetGlyph('i');
                                    if (null != smallCapsI) _stemV = smallCapsI.XMax - smallCapsI.XMin;
                                }
                            }
                            // If no glyphs could be read then the file is invalid.
                            else
                            {
                                throw new TrueTypeFontParseException("No glyph data could be read from the font file");
                            }
                        }
                    }
                    // If no tables could be read then the file is invalid.
                    else
                    {
                        throw new TrueTypeFontParseException("Could not read the tables in this font file");
                    }
                }
                // If the offset table could not be read then the file is invalid.
                else
                {
                    throw new TrueTypeFontParseException("Could not read the offset table in this font file");
                }
            }
            // If an I/O error occurred we rethrow as a TrueTypeFontParseException, and include the original
            // exception for reference.
            catch (IOException ex)
            {
                throw new TrueTypeFontParseException("An I/O error occurred while reading font data from the file", ex);
            }
        } // ParseFontStream()

        /// <summary>
        /// Reads the character mappings from the stream.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tables">The tables in the stream</param>
        /// <returns>The character mappings</returns>
        private CharacterMapping[] ReadCharacterMappings(Stream stream, Table[] tables)
        {
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);

            CharacterMapping[] results = null;

            Table cmapTable = FindTable(tables, "cmap");
            if (null != cmapTable)
            {
                // seek to the table, and then to the appropriate Windows Unicode or Windows Symbol subtable.
                stream.Seek(cmapTable.Offset, SeekOrigin.Begin);
                if (true == SeekToCmapSubtable(stream, cmapTable, 3, true==_isSymbolFont?0:1))
                    results = ReadCmapSubTable(stream);
                
            }

            return results;
        } // ReadCharacterMappings()

        /// <summary>
        /// Reads the Character mappings from the cmap sub-table that the specified stream currently points at.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        private CharacterMapping[] ReadCmapSubTable(Stream stream)
        {
            Debug.Assert(null != stream);

            List<CharacterMapping> mappings = new List<CharacterMapping>();

            // Read the subtable header.
            int format = ReadUShortValue(stream);
            int length = ReadUShortValue(stream);
            int version = ReadUShortValue(stream);
            int segments = ReadUShortValue(stream) / 2;

            // Read past stuff we're not interested in
            stream.Seek(6, SeekOrigin.Current);

            // the segments are in a block that is (4*count)+1 16-byte USHORTs long.
            // the glyph IDs are in a block next to that.
            byte[] segmentsBuffer = new byte[((4 * segments) + 1) * 2];
            stream.Read(segmentsBuffer, 0, segmentsBuffer.Length);
            byte[] glyphIdsBuffer = new byte[length];
            stream.Read(glyphIdsBuffer, 0, glyphIdsBuffer.Length);

            // read the character offsets now
            int endCounts = 0;
            int startCounts = (segments+1)*2;
            int idDeltas = startCounts +(segments*2);
            int idRangeOffsets = idDeltas + (segments*2);
            for (int segmentEnum = 0; segmentEnum < segments; segmentEnum++)
            {
                int startCharacter = ReadUShortValue(segmentsBuffer, startCounts + (segmentEnum * 2));
                int endCharacter = ReadUShortValue(segmentsBuffer, endCounts + (segmentEnum * 2));
                int idRangeOffset = ReadUShortValue(segmentsBuffer, idRangeOffsets + (segmentEnum * 2));
                int idDelta = ReadUShortValue(segmentsBuffer, idDeltas + (segmentEnum * 2));

                if (endCharacter != 0xffff)
                {
                    for (int character = startCharacter; character <= endCharacter; character++)
                    {
                        if (0 == idRangeOffset)
                            mappings.Add(new CharacterMapping(character, (character + 1 + idDelta) % 0x10000));
                        else
                        {
                            int characterIndex = idRangeOffset + ((character - startCharacter) * 2) - ((segments - segmentEnum) * 2);
                            int glyphId = ReadUShortValue(glyphIdsBuffer, characterIndex) + 1;
                            if (glyphId != 0)
                                if(true==_isSymbolFont)
                                    mappings.Add(new CharacterMapping(character % 0xF000, (glyphId + idDelta) % 0x10000));
                                else                                    
                                    mappings.Add(new CharacterMapping(character, (glyphId + idDelta) % 0x10000));
                        }

                    }
                }
            }

            return mappings.ToArray();
        } // ReadCmapSubTable()

        /// <summary>
        /// Reads the global header data from the stream and stores it in instance properties.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tables">The tables in the stream</param>
        private FontHeaders ReadFontHeaders(Stream stream, Table[] tables)
        {
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);

            FontHeaders headers = new FontHeaders();

            Table headTable = FindTable(tables, "head");
            if (null != headTable)
            {
                // seek to the 'head' table, and load the properties.
                stream.Seek(headTable.Offset, SeekOrigin.Begin);

                ReadFixedValue(stream); // table version (ignore)
                _revision = ReadFixedValue(stream); // revision
                ReadULongValue(stream); // checksum (ignore)
                ReadULongValue(stream); // magic (ignore)
                ReadUShortValue(stream); // flags (ignore)
                headers.UnitsPerEm = ReadUShortValue(stream); // unitsPerEm
                _unitsPerEm = headers.UnitsPerEm;
                _dateCreated = ReadLongDateTimeValue(stream); // created
                _dateModified = ReadLongDateTimeValue(stream); // modified
                _xMin = ReadFWordValue(stream); // xmin
                _yMin = ReadFWordValue(stream); // ymin
                _xMax = ReadFWordValue(stream); // xmax
                _yMax = ReadFWordValue(stream); // ymax
                ReadUShortValue(stream); // macStyle (ignore)
                ReadUShortValue(stream); // smallest pixel size (ignore)
                ReadShortValue(stream); // font direction hint (ignore)
                headers.IndexToLocFormat =  ReadShortValue(stream); // indexToLocFormat
            }

            return headers;
        } // ReadFontHeaders()

        /// <summary>
        /// Reads the glypth data from the stream.
        /// </summary>
        /// <param name="offsets">The offsets, in bytes, of each glyph in the file</param>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tables">The tables in the stream</param>
        /// <param name="metrics">The horizontal metrics for the font.</param>
        /// <returns>The glyph elements</returns>
        private GlyphInfo[] ReadGlyphData(long[] offsets, Stream stream, Table[] tables, HorizontalMetric[] metrics)
        {
            Debug.Assert(null != offsets);
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);
            Debug.Assert(null != metrics);

            List<GlyphInfo> glyphs = new List<GlyphInfo>();

            Table glyfTable = FindTable(tables, "glyf");
            if (null != glyfTable)
            {
                // seek to the 'glyf' table, and read the glyph data.
                stream.Seek(glyfTable.Offset, SeekOrigin.Begin);

                // Read each glyph's bounding box.
                for (int glyphEnum = 0; glyphEnum < Math.Min(offsets.Length - 1, metrics.Length); glyphEnum++)
                {
                    GlyphInfo glyph = new GlyphInfo();
                    glyph.Metric = metrics[glyphEnum];
                    long glyphDataLength = offsets[glyphEnum + 1]-offsets[glyphEnum];
                    if (glyphDataLength > 0)
                    {
                        ReadShortValue(stream); // ignore contours
                        glyph.XMin = ReadShortValue(stream);
                        glyph.YMin = ReadShortValue(stream);
                        glyph.XMax = ReadShortValue(stream);
                        glyph.YMax = ReadShortValue(stream);

                        // .. skip the rest of the glyph's data
                        stream.Seek(glyphDataLength-10, SeekOrigin.Current);
                    }

                    // Add the glyph to the list.
                    glyphs.Add(glyph);
                }
            }

            return glyphs.ToArray();
        } // ReadGlyphData

        /// <summary>
        /// Reads the glyph data from the stream.
        /// </summary>
        /// <param name="glyphCount">The number of glyphs to read</param>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tables">The tables in the stream</param>
        /// <param name="headers">The font's headers.</param>
        /// <returns>The glyph offsets</returns>
        private long[] ReadGlyphOffsets(int glyphCount, Stream stream, Table[] tables, FontHeaders headers)
        {
            Debug.Assert(glyphCount >= 0);
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);
            Debug.Assert(null != headers);

            long[] offsets = new long[glyphCount+1];

            Table locaTable = FindTable(tables, "loca");
            if (null != locaTable)
            {
                // seek to the 'loca' table to extract the glyph offsets.
                stream.Seek(locaTable.Offset, SeekOrigin.Begin);

                // If we use format #0, we read ushorts, otherwise we read ulongs.
                if (0 == headers.IndexToLocFormat)
                {
                    for (int glyphEnum = 0; glyphEnum <= glyphCount; glyphEnum++)
                        offsets[glyphEnum] = ReadUShortValue(stream) * 2;
                }
                else
                {
                    for (int glyphEnum = 0; glyphEnum <= glyphCount; glyphEnum++)
                        offsets[glyphEnum] = ReadULongValue(stream);
                }
            }

            return offsets;
        } // ReadGlyphOffsets()

        /// <summary>
        /// Reads the glyph data from the stream.
        /// </summary>
        /// <param name="glyphCount">The number of glyphs to read</param>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tables">The tables in the stream</param>
        /// <param name="headers">The font's headers</param>
        /// <param name="metrics">The horizontal metrics for the font.</param>
        /// <returns>The glyph elements</returns>
        private GlyphInfo[] ReadGlyphs(int glyphCount, Stream stream, Table[] tables, FontHeaders headers, HorizontalMetric[] metrics)
        {
            Debug.Assert(glyphCount >= 0);
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);
            Debug.Assert(null != headers);

            GlyphInfo[] results = null;

            if (null != metrics)
            {
                // First we read the glyph offsets.
                long[] offsets = ReadGlyphOffsets(glyphCount, stream, tables, headers);

                // Then, using the offsets we read the glyph data.
                results = ReadGlyphData(offsets, stream, tables, metrics);
            }

            return results;
        } // ReadGlyphs()

        /// <summary>
        /// Reads the horizontal headers of this font.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <param name="tables">The tables in the stream</param>
        /// <returns>The number of horizontal metrics.</returns>
        private int ReadHorizontalHeaders(Stream stream, Table[] tables)
        {
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);

            int numberOfHMetrics = 0;

            Table headTable = FindTable(tables, "hhea");
            if (null != headTable)
            {
                // seek to the 'head' table, and load the properties.
                stream.Seek(headTable.Offset, SeekOrigin.Begin);

                ReadFixedValue(stream); // table version (ignore)
                _ascender = ReadFWordValue(stream); // typographic ascender
                _descender = ReadFWordValue(stream); // typographic descender

                // skip the next 13 entries
                stream.Seek(26, SeekOrigin.Current);

                numberOfHMetrics = ReadUShortValue(stream); // number of H-Metrics

            }

            return numberOfHMetrics;
        } // ReadHorizontalHeaders()

        /// <summary>
        /// Reads the horizontal metrics of this font.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <param name="tables">The tables in the stream</param>
        /// <param name="horizontalMetricsCount">The number of horizontal metrics in the font.</param>
        /// <param name="glyphCount">The number of glyphs in the font.</param>
        /// <returns>The horizontal metrics.</returns>
        private HorizontalMetric[] ReadHorizontalMetrics(Stream stream, Table[] tables, int horizontalMetricsCount, int glyphCount)
        {
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);

            List<HorizontalMetric> metrics = new List<HorizontalMetric>();

            Table hmtxTable = FindTable(tables, "hmtx");
            if (null != hmtxTable)
            {
                // seek to the 'hmtx' table, and load the properties.
                stream.Seek(hmtxTable.Offset, SeekOrigin.Begin);

                // Read all the metrics first.
                for (int metricEnum = 0; metricEnum < horizontalMetricsCount; metricEnum++)
                {
                    HorizontalMetric metric = new HorizontalMetric();
                    metric.AdvanceWidth = ReadUShortValue(stream);
                    metric.LeftSideBearing = ReadFWordValue(stream);

                    metrics.Add(metric);
                }

                // If the number of glyphs is larger than the number of metrics, we load the left-side bearings
                // for the remainder of the glyphs.
                if (glyphCount > horizontalMetricsCount)
                {
                    HorizontalMetric baseMetric = metrics[horizontalMetricsCount-1];
                    //metrics.Remove(baseMetric);

                    for (int remainderEnum = 0; remainderEnum < (glyphCount - horizontalMetricsCount); remainderEnum++)
                    {
                        HorizontalMetric metric = new HorizontalMetric();
                        metric.AdvanceWidth = baseMetric.AdvanceWidth;
                        metric.LeftSideBearing = ReadFWordValue(stream);

                        metrics.Add(metric);
                    }
                }
            }

            return metrics.ToArray();
        } // ReadHorizontalMetrics()

        /// <summary>
        /// Reads the maximum profile for the font from the stream.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="tables">The tables in the stream</param>
        /// <returns>The number of glyphs</returns>
        private int ReadMaximumProfile(Stream stream, Table[] tables)
        {
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);

            int numberOfGlyphs = 0;

            Table maxpTable = FindTable(tables, "maxp");
            if (null != maxpTable)
            {
                // seek to the table, and read the properties.
                stream.Seek(maxpTable.Offset, SeekOrigin.Begin);

                decimal version = ReadFixedValue(stream);
                numberOfGlyphs = ReadUShortValue(stream);
            }

            return numberOfGlyphs;
        } // ReadMaximumProfile()

        /// <summary>
        /// Reads the name properties for this font.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <param name="tables">The tables in the stream</param>
        private void ReadNameData(Stream stream, Table[] tables)
        {
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);

            Table nameTable = FindTable(tables, "name");
            if (null != nameTable)
            {
                // seek to the 'head' table, and load the properties.
                stream.Seek(nameTable.Offset, SeekOrigin.Begin);

                ReadUShortValue(stream); // ignore this
                int recordCount = ReadUShortValue(stream); // number of name records
                long stringTableOffset = ReadUShortValue(stream) + nameTable.Offset;

                // Read through the records until we find a postscript name.
                // This will be a record with platform ID 3, Encoding ID 1 (0 for symbol fonts) and name ID 6.
                int ourEncoding = _isSymbolFont ? 0 : 1;
                int recordEnum = 0;
                while (recordEnum < recordCount && (null == _postscriptName || null == _fullName))
                {
                    // Read the data
                    int platformId = ReadUShortValue(stream);
                    int encodingId = ReadUShortValue(stream);
                    int languageId = ReadUShortValue(stream);
                    int nameId = ReadUShortValue(stream);
                    int stringLength = ReadUShortValue(stream);
                    int stringOffset = ReadUShortValue(stream);

                    // If we have found the right record, we read the data.
                    if (platformId == 3 && (0 == encodingId || 1 == encodingId))
                    {

                        if (6 == nameId)
                            _postscriptName = ReadNameString(stream, stringTableOffset + stringOffset, stringLength);

                        if (4 == nameId)
                            _fullName = ReadNameString(stream, stringTableOffset + stringOffset, stringLength); ;

                    } // if we found the record

                    // next record
                    recordEnum++;
                } // for each record
            } // if there is a name table
        } // ReadNameData()

        /// <summary>
        /// Reads a name string off the specified stream, as indicated by the offset and string length.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <param name="offset">The offset of the string in the stream</param>
        /// <param name="stringLength">The length of the string</param>
        /// <returns>The string</returns>
        private string ReadNameString(Stream stream, long offset, int stringLength)
        {
            long current = stream.Position;
            stream.Seek(offset, SeekOrigin.Begin);
            byte[] stringBuffer = new byte[stringLength];
            stream.Read(stringBuffer, 0, stringLength);
            stream.Seek(current, SeekOrigin.Begin);

            return System.Text.Encoding.BigEndianUnicode.GetString(stringBuffer);
        }

        /// <summary>
        /// Reads the table directory and returns its contents.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <returns>The number of tables in the font file.</returns>
        private int ReadOffsetTable(Stream stream)
        {
            Debug.Assert(null != stream);

            decimal version = ReadFixedValue(stream);
            int numberOfTables = ReadUShortValue(stream);

            // Ignore the following three entries.
            ReadUShortValue(stream);
            ReadUShortValue(stream);
            ReadUShortValue(stream);

            return numberOfTables;
        }

        /// <summary>
        /// Reads the OS/2 properties for this font.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <param name="tables">The tables in the stream</param>
        private void ReadOs2Data(Stream stream, Table[] tables)
        {
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);

            Table os2Table = FindTable(tables, "OS/2");
            if (null != os2Table)
            {
                // seek to the 'head' table, and load the properties.
                stream.Seek(os2Table.Offset, SeekOrigin.Begin);

                stream.Seek(68, SeekOrigin.Current); // skip the properties we are not interested in
                _ascender = ReadFWordValue(stream); // typographic ascender
                _descender = ReadFWordValue(stream); // typographic descender            

                // Read the codepage data to see if this is a symbol font or unicode font
                stream.Seek(6, SeekOrigin.Current); // skip the properties we are not interested in
                long codePage = ReadULongValue(stream); // Codepage data
                if ((codePage & 0x80000000) > 0) _isSymbolFont = true;
            }
        } // ReadOs2Data()

        /// <summary>
        /// Reads the postscript properties for this font.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <param name="tables">The tables in the stream</param>
        private void ReadPostscriptData(Stream stream, Table[] tables)
        {
            Debug.Assert(null != stream);
            Debug.Assert(null != tables);

            Table postTable = FindTable(tables, "post");
            if (null != postTable)
            {
                // seek to the 'head' table, and load the properties.
                stream.Seek(postTable.Offset, SeekOrigin.Begin);

                ReadFixedValue(stream); // format type
                _italicAngle = ReadFixedValue(stream); // Italic angle
            }
        }

        /// <summary>
        /// Reads the table directory from the stream and returns the tables.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <param name="tableCount">The number of tables to read</param>
        /// <returns>The tables</returns>
        private Table[] ReadTableDirectory(Stream stream, int tableCount)
        {
            Debug.Assert(null != stream);

            List<Table> tables = new List<Table>();

            for (int tableEnum = 0; tableEnum < tableCount; tableEnum++)
            {
                string tag = ReadStringValue(stream, 4);
                long checkSum = ReadULongValue(stream);
                long offset = ReadULongValue(stream);
                long length = ReadULongValue(stream);

                tables.Add(new Table(tag, checkSum, offset, length));
            }

            return tables.ToArray();
        } // ReadTableDirectory()

        /// <summary>
        /// Seeks to the sub-table in the 'cmap' table that corresponds to the specified platform ID and
        /// encoding ID.
        /// </summary>
        /// <param name="stream">The stream to read from</param>
        /// <param name="cmapTable">The 'cmap' table</param>
        /// <param name="platformId">The platform ID to look for</param>
        /// <param name="encodingId">The encoding ID to look for</param>
        /// <returns>A boolean indicating if the operation was successful (true) or not</returns>
        private bool SeekToCmapSubtable(Stream stream, Table cmapTable, int platformId, int encodingId)
        {
            Debug.Assert(null != stream);
            Debug.Assert(null != cmapTable);

            bool isSuccess = false;

            ReadUShortValue(stream); // ignore table version
            int numberOfEncodingTables = ReadUShortValue(stream);

            // We go through each of the mapping tables until we find the one with the desired platform ID 
            // and Encoding ID.
            for (int encodingEnum = 0; encodingEnum < numberOfEncodingTables; encodingEnum++)
            {
                int thisPlatformId = ReadUShortValue(stream);
                int thisEncodingId = ReadUShortValue(stream);
                long subtableOffset = ReadULongValue(stream);

                // We have found the subtable we're looking for.
                if (platformId == thisPlatformId && encodingId == thisEncodingId)
                {
                    // Seek to the subtable
                    stream.Seek(subtableOffset + cmapTable.Offset, SeekOrigin.Begin);
                    isSuccess = true;
                    break;
                }
            }

            return isSuccess;
        } // SeekToCmapSubtable()
        #endregion

        #region Stream reading
        /// <summary>
        /// Reads a 32-bits fixed-point from the stream.
        /// </summary>
        /// <param name="stream">The stream</param>
        /// <returns>The fixed-point value</returns>
        private decimal ReadFixedValue(Stream stream)
        {
            short mantissa = (short) ((short)(stream.ReadByte() << 8) + (short)stream.ReadByte());
            int fraction = stream.ReadByte() << 8 | stream.ReadByte();

            return mantissa + (fraction / 65536m);
        }

        /// <summary>
        /// Reads a DateTime value from the stream. This is understood to be a long (8-byte) number that encodes
        /// the number of seconds since 1904.
        /// </summary>
        /// <returns>The datetime value.</returns>
        private DateTime ReadLongDateTimeValue(Stream stream)
        {
            ulong seconds = 0;
            for (int part = 0; part < 8; part++)
            {
                seconds = seconds << 8;
                seconds = seconds + (ulong)stream.ReadByte();
            }

            DateTime result = new DateTime(1904, 1, 1);
            if (seconds < 10000000000)
                try { result = result.AddSeconds((double)seconds); }
                catch (ArgumentOutOfRangeException) { }
            
            return result;
        }

        /// <summary>
        /// Reads a FWORD value from the stream.
        /// </summary>
        /// <param name="stream">The stream</param>
        /// <returns>The FWORD value</returns>
        private int ReadFWordValue(Stream stream)
        {
            return ReadShortValue(stream);
        }

        /// <summary>
        /// Reads a SHORT value from the stream.
        /// </summary>
        /// <param name="stream">The stream</param>
        /// <returns>The SHORT value</returns>
        private int ReadShortValue(Stream stream)
        {
            short value = (short)(stream.ReadByte() << 8);
            value += (short)stream.ReadByte();
            return value;
        }

        /// <summary>
        /// Reads a USHORT value from the stream.
        /// </summary>
        /// <param name="stream">The stream</param>
        /// <returns>The USHORT value</returns>
        private int ReadUShortValue(Stream stream)
        {
            return stream.ReadByte() << 8 | stream.ReadByte();
        }

        /// <summary>
        /// Reads a USHORT value from a buffer.
        /// </summary>
        /// <param name="buffer">The buffer</param>
        /// <param name="offset">The offset in the buffer to start reading at.</param>
        /// <returns>The USHORT value</returns>
        private int ReadUShortValue(byte[] buffer, int offset)
        {
            return buffer[offset] << 8 | buffer[offset+1];
        }
        /// <summary>
        /// Reads a ULONG value from the stream.
        /// </summary>
        /// <param name="stream">The stream</param>
        /// <returns>The ULONG value</returns>
        private long ReadULongValue(Stream stream)
        {
            long value = 0;
            value += stream.ReadByte() << 24;
            value += stream.ReadByte() << 16;
            value += stream.ReadByte() << 8;
            value += stream.ReadByte();

            return value;
        }

        /// <summary>
        /// Reads a fixed-length string from the supplied stream.
        /// </summary>
        /// <param name="stream">The stream to read from.</param>
        /// <param name="length">The length of the stream to read.</param>
        /// <returns>The string value.</returns>
        private string ReadStringValue(Stream stream, int length)
        {
            StringBuilder sb = new StringBuilder();
            for (int n = 0; n < length; n++)
                sb.Append((char)(stream.ReadByte()));

            return sb.ToString();
        }
        #endregion



        // Public constructor
        // ==================
        #region Public constructor
        /// <summary>
        /// Constructs a new TrueTypeFontInfo instance.
        /// </summary>
        public TrueTypeFontInfo()
        {
            _mappedGlyphs = new Dictionary<int, GlyphInfo>();
        }
        #endregion

    } // TrueTypeFontInfo class
}
